package code.easy.refresh

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue


internal class RefreshNestedScrollConnection(
    private val refreshState: RefreshState,
    private val coroutineScope: CoroutineScope,
) : NestedScrollConnection {
    var enabled: Boolean = true
    var maxOffsetMultipleTrigger: Float = 2.5f
    var refreshTrigger: Float = 0f

    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        return when {
            !enabled -> Offset.Zero
            refreshState.state == RefreshState.State.Refresh -> Offset.Zero
            refreshState.state == RefreshState.State.Finish -> Offset.Zero
            source == NestedScrollSource.Drag && available.y < 0 -> onPull(available)
            else -> Offset.Zero
        }
    }

    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset {
        return when {
            !enabled -> Offset.Zero
            refreshState.state == RefreshState.State.Refresh -> Offset.Zero
            refreshState.state == RefreshState.State.Finish -> Offset.Zero
            source == NestedScrollSource.Drag && available.y > 0 -> onPull(available)
            else -> Offset.Zero
        }
    }

    override suspend fun onPreFling(available: Velocity): Velocity {
        if (refreshState.state != RefreshState.State.Pull) return Velocity.Zero
        if (refreshState.indicatorOffset >= refreshTrigger) {
            refreshState.state = RefreshState.State.Refresh
        } else {
            refreshState.state = RefreshState.State.Close
        }
        return Velocity.Zero
    }

    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
        return Velocity(x = 0f, y = available.y)
    }

    private fun onPull(available: Offset): Offset {
        val damping = if (maxOffsetMultipleTrigger != 0f) {
            val maxOffset = refreshTrigger * maxOffsetMultipleTrigger
            1 - refreshState.indicatorOffset / maxOffset
        } else {
            1f
        }
        val newOffset = (available.y * damping + refreshState.indicatorOffset).coerceAtLeast(0f)
        val dragConsumed = newOffset - refreshState.indicatorOffset
        return if (dragConsumed.absoluteValue >= 0.5f) {
            coroutineScope.launch {
                refreshState.state = RefreshState.State.Pull
                refreshState.dispatchScrollDelta(dragConsumed)
            }
            Offset(x = 0f, y = available.y)
        } else {
            Offset.Zero
        }
    }
}
